package com.xfsg.core.log.appender.logback;

import ch.qos.logback.classic.spi.IThrowableProxy;
import ch.qos.logback.classic.spi.LoggingEvent;
import ch.qos.logback.classic.spi.StackTraceElementProxy;
import ch.qos.logback.classic.spi.ThrowableProxyUtil;
import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.UnsynchronizedAppenderBase;
import ch.qos.logback.core.encoder.Encoder;
import com.aliyun.openservices.aliyun.log.producer.LogProducer;
import com.aliyun.openservices.aliyun.log.producer.errors.LogSizeTooLargeException;
import com.aliyun.openservices.aliyun.log.producer.errors.ProducerException;
import com.aliyun.openservices.aliyun.log.producer.errors.TimeoutException;
import com.aliyun.openservices.log.common.LogItem;
import com.xfsg.core.log.appender.AliYunAppenderCallback;
import com.xfsg.core.log.config.AliyunLogItem;
import com.xfsg.core.log.config.AliyunLogProperties;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;

/**
 * @Classname LogbackAliYunAppender
 * @Description Logback日志构造
 * @date 2021/12/16 11:28
 * @author ZhangYi
 */
public class LogbackAliYunAppender<E> extends UnsynchronizedAppenderBase<E> {

	private static final Logger logger = LoggerFactory.getLogger(LogbackAliYunAppender.class);
	public static final String userAgent = "logback";

    private Encoder<E> encoder;

    protected AliyunLogProperties config;

	protected AliyunLogItem logItem;

    private LogProducer producer;

    private DateTimeFormatter formatter;

    public LogbackAliYunAppender() {
    }

    public LogbackAliYunAppender(AliyunLogProperties logAppenderConfig, AliyunLogItem logItem) {

        this.config = logAppenderConfig;
		this.logItem = logItem;
    }

    @Override
    public void start() {
        try {
            doStart();
        } catch (Exception e) {
			logger.error("Failed to start LogbackAliYunAppender.",e);
            addError("Failed to start LogbackAliYunAppender.", e);
        }
    }

    private void doStart() {
        formatter = DateTimeFormat.forPattern(config.getTimeFormat()).withZone(DateTimeZone.forID(config.getTimeZone()));
        producer = new LogProducer(config.getProducer().buildProducerConfig());
		producer.putProjectConfig(config.getProject().buildProjectConfig(userAgent));
        super.start();
    }

    @Override
    public void stop() {
        try {
            doStop();
        } catch (Exception e) {
            addError("Failed to stop LogbackAliYunAppender.", e);
        }
    }

    private void doStop() throws InterruptedException, ProducerException {
        if (!isStarted()){
            return;
        }
        super.stop();
        if (producer != null) {
            producer.close();
        }
    }

    @Override
    public void append(E eventObject) {

        //init Event Object
        if (!(eventObject instanceof LoggingEvent)) {
            return;
        }
        LoggingEvent event = (LoggingEvent) eventObject;

        List<LogItem> logItems = new ArrayList<LogItem>();
        LogItem item = new LogItem();


        logItems.add(item);
        item.SetTime((int) (event.getTimeStamp() / 1000));

        DateTime dateTime = new DateTime(event.getTimeStamp());
        item.PushBack("time", dateTime.toString(formatter));
        item.PushBack("level", event.getLevel().toString());
        item.PushBack("thread", event.getThreadName());
		item.PushBack("app-name",logItem.getServiceId());
        StackTraceElement[] caller = event.getCallerData();
        if (caller != null && caller.length > 0) {
            item.PushBack("location", caller[0].toString());
        }

        String message = event.getFormattedMessage();
        item.PushBack("message", message);

        IThrowableProxy iThrowableProxy = event.getThrowableProxy();
        if (iThrowableProxy != null) {
            String throwable = getExceptionInfo(iThrowableProxy);
            throwable += fullDump(event.getThrowableProxy().getStackTraceElementProxyArray());
            item.PushBack("throwable", throwable);
        }

        if (this.encoder != null) {
            item.PushBack("log", new String(this.encoder.encode(eventObject)));
        }

        try {
            producer.send(
                    config.getProject().getName(),
                    config.getProject().getLogstore(),
                    config.getTopic(),
                    null,
                    logItems,
                    new AliYunAppenderCallback(config, logItems));
        } catch (InterruptedException e) {
            addError("The current thread has been interrupted during send logs.");
        } catch (Exception e) {
            if (e instanceof LogSizeTooLargeException) {
                addError("The size of log is larger than the maximum allowable size, e={}", e);
            } else if (e instanceof TimeoutException) {
                addError("The time taken for allocating memory for the logs has surpassed., e={}", e);
            } else {
                addError("Failed to send log, e=", e);
            }
        }
    }

    private String getExceptionInfo(IThrowableProxy iThrowableProxy) {
        String s = iThrowableProxy.getClassName();
        String message = iThrowableProxy.getMessage();
        return (message != null) ? (s + ": " + message) : s;
    }

    private String fullDump(StackTraceElementProxy[] stackTraceElementProxyArray) {
        StringBuilder builder = new StringBuilder();
        for (StackTraceElementProxy step : stackTraceElementProxyArray) {
            builder.append(CoreConstants.LINE_SEPARATOR);
            String string = step.toString();
            builder.append(CoreConstants.TAB).append(string);
            ThrowableProxyUtil.subjoinPackagingData(builder, step);
        }
        return builder.toString();
    }

    public AliyunLogProperties getConfig() {
        return config;
    }

    public void setConfig(AliyunLogProperties config) {
        this.config = config;
    }

    public void setEncoder(Encoder<E> encoder) {
        this.encoder = encoder;
    }
}
